package com.parsechina.hpush.broker.spi.persistence;

import com.parsechina.hpush.broker.internal.MessageGUID;

import java.io.Serializable;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicReference;

/**
 * @author linfeng
 * @since 2016/12/6
 */
public class GuidIndex implements Serializable{

    private static final long serialVersionUID = 1803317773018363407L;

    public static class GuidNode implements Serializable{
        private static final long serialVersionUID = -6557315260705507433L;
        public MessageGUID guid;
        String content;
        boolean isEnd;
        public int count;
        public Map<String, GuidNode> nodeMap;

        GuidNode(String word) {
            nodeMap = new ConcurrentHashMap<>(10);
            isEnd = false;
            content = word;
            count = 0;
        }

        GuidNode(String content, MessageGUID guid) {
            this(content);
            this.guid = guid;
        }

        GuidNode copy() {
            final GuidNode copy = new GuidNode(content, guid);
            copy.nodeMap = nodeMap;
            copy.count = count;
            copy.isEnd = isEnd;
            return copy;
        }

        public GuidNode childNodes(String word) {
            return nodeMap.get(word);
        }

        public void setGuid(MessageGUID guid) {
            this.guid = guid;
        }

    }

    private final AtomicReference<GuidNode> root;

    public GuidIndex() {
        root = new AtomicReference<>(new GuidNode(" "));
    }

    private StringTokenizer initToken(String word) {
        return new StringTokenizer(word, "/");
    }

    public void insert(String word, MessageGUID guid) {
        if (searchAndUpdate(word, guid)) {
            return;
        }
        GuidNode oldRoot;
        GuidNode newRoot;
        do {
            oldRoot = root.get();
            newRoot = oldRoot.copy();
            GuidNode current = newRoot;
            StringTokenizer tokenizer = initToken(word);
            while (tokenizer.hasMoreTokens()) {
                String word1 = tokenizer.nextToken();
                GuidNode child = current.childNodes(word1);
                if (child != null) {
                    current = child;
                } else {
                    current.nodeMap.put(word1, new GuidNode(word1, guid));
                    current = current.childNodes(word1);
                }
                current.count++;
            }
            current.isEnd = true;
        } while (!root.compareAndSet(oldRoot, newRoot));

    }

    public boolean searchAndUpdate(String word, MessageGUID guid) {
        GuidNode current = root.get();

        StringTokenizer tokenizer = initToken(word);
        while (tokenizer.hasMoreTokens()) {
            String word1 = tokenizer.nextToken();
            if (current.childNodes(word1) == null) {
                return false;
            } else {
                current = current.childNodes(word1);
            }
        }
        if (current.isEnd) {
            current.guid = guid;
        }
        return current.isEnd;

    }

    public boolean search(String word) {
        GuidNode current = root.get();

        StringTokenizer tokenizer = initToken(word);
        while (tokenizer.hasMoreTokens()) {
            String word1 = tokenizer.nextToken();
            if (current.childNodes(word1) == null) {
                return false;
            } else {
                current = current.childNodes(word1);
            }
        }
        return current.isEnd;

    }

    public GuidNode searchPrefix(String word) {
        GuidNode current = root.get();

        StringTokenizer tokenizer = initToken(word);
        while (tokenizer.hasMoreTokens()) {
            String word1 = tokenizer.nextToken();
            if (current.childNodes(word1) == null) {
                return current;
            } else {
                current = current.childNodes(word1);
            }
        }
        return current;
    }

    public MessageGUID searchOne(String word) {
        GuidNode current = root.get();

        StringTokenizer tokenizer = initToken(word);
        while (tokenizer.hasMoreTokens()) {
            String word1 = tokenizer.nextToken();
            if (current.childNodes(word1) == null) {
                return null;
            } else {
                current = current.childNodes(word1);
            }
        }
        return current.guid;
    }

    public List<GuidNode> complexSearch(final String prefix) {

        Deque<GuidNode> nodes = new ArrayDeque<>(1);
        nodes.add(root.get());
        List<GuidNode> results = new ArrayList<>(10);
        StringTokenizer tokenizer = initToken(prefix);
        while (tokenizer.hasMoreTokens()) {

            String word1 = tokenizer.nextToken();

            if (!results.isEmpty()) {
                nodes.addAll(results);
                results.clear();
            }

            GuidNode current = nodes.poll();

            if ("#".equals(word1)) {
                for (; current != null; current = nodes.poll()) {
                    for (GuidNode entry : current.nodeMap.values()) {
                        results.add(entry);
                    }
                }
                break;
            }
            if ("+".equals(word1)) {
                for (; current != null; current = nodes.poll()) {
                    for (GuidNode entry : current.nodeMap.values()) {
                        results.add(entry);
                    }
                }
                continue;
            }

            for (; current != null; current = nodes.poll()) {

                GuidNode guidNode = current.childNodes(word1);
                if (guidNode != null) {
                    results.add(guidNode);
                }
            }

        }

        return results;
    }

    public void listAllGuidOfNode(GuidNode node, List<MessageGUID> results) {
        if (node.isEnd) {
            results.add(node.guid);
        }
        for (GuidNode entry : node.nodeMap.values()) {
            listAllGuidOfNode(entry, results);
        }
    }

    public void deleteWord(String word) {
        if (!search(word)) {
            return;
        }

        GuidNode oldRoot;
        GuidNode newRoot;
        do {

            oldRoot = root.get();
            newRoot = oldRoot.copy();
            GuidNode current = newRoot;
            StringTokenizer tokenizer = initToken(word);
            while (tokenizer.hasMoreTokens()) {
                String word1 = tokenizer.nextToken();
                GuidNode child = current.childNodes(word1);
                if (child.count == 1) {
                    current.nodeMap.remove(child.content);
                    return;
                } else {
                    child.count--;
                    current = child;
                }
            }
            current.isEnd = false;

        } while (!root.compareAndSet(oldRoot, newRoot));

    }

}
